/* DatePickerWidget.js
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

$package("dowry.widget");

/**
 * DatePickerWidget provides a text input field with the
 * option of providing date entry via selecting it off of
 * a popup calendar.
 *
 * <p>
 * This class uses
 * <a href="http://www.dynarch.com/projects/calendar">
 * JSCalendar</a> to provide the popup calendar
 * functionality.  Since this code is not bundled with
 * Dowry, please be sure to include the associated
 * scripts, stylesheets, etc. for this external library.
 * </p>
 *
 */
$class("DatePickerWidget").$extends("TextInputWidget").$as(
{
    /**
     * Constant for the label of the button that activates
     * the popup calendar.
     *
     */
    BUTTON_LABEL : "...",

    /**
     * Constant for the prefix to place before the ID of the
     * button to active the popup calendar, to make the
     * identifier unique.
     *
     */
    BUTTON_PREFIX : "_dowry_widget_DatePickerWidget_btn_",

    /**
     * Constant for the prefix to place before the ID of the
     * hidden field that stores the actual date represented
     * by the popup calendar.
     *
     */
    HIDDEN_PREFIX : "_dowry_widget_DatePickerWidget_hidden_",

    /**
     * Constant for the CSS class name to apply to the
     * datepicker button.
     *
     */
    CSS_BUTTON : "datepicker_button",

    /**
     * Property representing the string ID of the element
     * representing the button activator of the date
     * picker's popup calendar.
     *
     */
    _btnId : null,

    /**
     * Property representing the internal date format used
     * to communicate date values to/from the date picker.
     * This has no bearing on the actual datatype's date
     * format... this is only used internally.
     *
     */
    _dateFormat : "%Y/%m/%d %H:%M:%S",

    /**
     * Property representing the string ID of the element
     * representing the hidden field used to maintain some
     * basic date picker state.  This is really a hack
     * to make this work nicely with JSCalendar.
     *
     */
    _hiddenFieldId : null,

    /**
     * Constructs the DatePickerWidget and builds the
     * button to activate the popup calendar.
     *
     * @param ele    the element (or ID of the element)
     *               to bind to this datatype
     *
     */
    DatePickerWidget : function(ele)
    {
        this.$super(arguments);

        // add the button to initiate the picker
        ele = this.getElement();
        var btn = this.createButton();
        var hiddenField = this.createHiddenField();

        if (ele &&
            btn &&
            btn.id &&
            hiddenField &&
            hiddenField.id)
        {
            var span = this._createEnclosingSpan(ele);
            span.appendChild(hiddenField);
            span.appendChild(btn);
            this._hiddenFieldId = hiddenField.id;
            this._btnId = btn.id;
        }
    },

    /**
     * Creates the button element used to activate the
     * date picker's popup calendar.  By default this
     * creates an element such as <code>
     * &lt;input type="button" value="..."/&gt;</code>
     *
     * @return the created elemnt represeting the button to
     *         activate the date picker's popup calendar
     *
     */
    createButton : function()
    {
        var btn = null;

        var ele = this.getElement();
        if (ele)
        {
            btn = document.createElement("input");
            btn.setAttribute("type", "button");
            btn.setAttribute("value", this.BUTTON_LABEL);
            btn.setAttribute("id", this.BUTTON_PREFIX + ele.id);
            Dowry.css.addClassName(btn, this.CSS_BUTTON);
        }

        return btn;
    },

    /**
     * Creates the <code>&lt;input type="hidden"/%gt;</code>
     * element.
     *
     * @return the hidden field
     *
     */
    createHiddenField : function()
    {
        var hiddenField = null;

        var ele = this.getElement();
        if (ele)
        {
            hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("id", this.HIDDEN_PREFIX + ele.id);
        }

        return hiddenField;
    },

    /**
     * Gets the mapping of JSCalendar parameters to use
     * with JSCalendar's <code>Calendar.setup()</code>
     * method.
     *
     * @return  an object containing the JSCalendar
     *          parameters
     *
     */
    getJsCalendarParams : function()
    {
        return new Object();
    },

    /**
     * Performs all the normal <code>setValue()</code>
     * logic, as well as keeps the hidden field in sync with
     * these updates made programatically.
     *
     * @param val     the value to set
     *
     * @param ignore  (optional) a boolean indicating
     *                whether or not to skip syncing the
     *                hidden field.  This is used internally
     *                when a date is selected, since
     *                JSCalendar will automatically update
     *                the hidden field appropriately.  In
     *                all other cases, this param should be
     *                omitted
     *
     */
    setValue : function(val, ignore)
    {
        this.$super(arguments);

        // sync with hidden field
        if (!ignore)
        {
            this._updateHiddenField();
        }
    },

    /**
     * When the DatePickerWidget is started, associate
     * events to validate the input, as well as the events
     * for the calendar activation / selection.
     *
     */
    start : function()
    {
        this.$super(arguments);

        var ele = this.getElement();

        if (ele &&
            this._btnId &&
            this._hiddenFieldId &&
            window.Calendar &&
            window.Calendar.setup)
        {
            // copy over any initial value
            this._updateHiddenField();

            // get the basic popup calendar setup
            var setup = this.getJsCalendarParams();
            if (!setup)
            {
                setup = new Object();
            }

            // set the popup calendar params that are
            // not overridable
            setup.inputField = this._hiddenFieldId;
            setup.button = this._btnId;
            setup.onUpdate = this._handleSelection;
            setup.ifFormat = this._dateFormat;

            // initialize the popup calendar
            Calendar.setup(setup);

            // add an event to handle any
            // future changes due to manual
            // entry into the text field
            Dowry.event.addEvent(ele, "change", this._updateHiddenField);
        }
    },

    /**
     * Handles the selection of a date on the popup
     * calendar.
     *
     * @param   cal   the js calendar object
     *
     */
    _handleSelection : function(cal)
    {
        // update the widget's value to
        // the selected date
        this.setValue(cal.date, true);

        // fire validation, if applicable...
        var dt = this.getDatatype();
        if (dt)
        {
            this.setErrorMessage(dt.validate(cal.date));
        }
    },

    /**
     * Updates the hidden field by copying over the value
     * from the "real" field into our hidden field, so
     * JSCalendar stays in sync.
     *
     */
    _updateHiddenField : function()
    {
        var ele = this.getElement();
        var dt = this.getDatatype();
        if (ele.value && dt)
        {
            var val = null;
            var hiddenField = $(this._hiddenFieldId);
            var d = dt.toDatatype(ele.value);
            if (hiddenField && d && d.print)
            {
                val = d.print(this._dateFormat);
            }

            hiddenField.value = val;
        }
    }
});